namespace Ex
{
    using KeraLua;

    public class ExLuaFunction : IExLuaFunction, IDisposable
    {

        private bool disposed = false;
        protected int RefId
        {
            get
            {
                if (!ref_id.HasValue)
                {
                    throw new InvalidOperationException("LuaFunction is not valid.");
                }
                return ref_id.Value;
            }
        }

        private readonly int? ref_id;

        protected EnvLua env;

        public EnvLua CurrentLua
        {
            get => env;
        }

        protected ExLuaFunction(EnvLua env, int ref_id)
        {
            this.env = env;
            this.ref_id = ref_id;
        }

        public static ExLuaFunction WatchFromLuaStack(EnvLua env, int index)
        {

            lock (env)
            {

                if (!env.Current.IsFunction(index))
                {
                    throw new Exception("unexpected error: value here should be luafunction");
                }
                env.Current.PushCopy(index);
                return new ExLuaFunction(env, env.Current.Ref(LuaRegistry.Index));
            }
        }

        public static ExLuaFunction FetchFromLuaStack(EnvLua env)
        {

            lock (env)
            {
                if (!env.Current.IsFunction(-1))
                {
                    throw new Exception("unexpected error: value here should be luafunction");
                }
                int ref_id = env.Current.Ref(LuaRegistry.Index);
                return new ExLuaFunction(env, ref_id);
            }
        }


        public static ExLuaFunction CreateFromCsharpString(EnvLua env, string lua_function)
        {
            lock (env)
            {
                env.Current.LoadString(lua_function);
                return FetchFromLuaStack(env);
            }
        }

        public IndefiniteVarType Call(UsableLuaType[] result_type, params object[] args)
        {
            return CallFromArray(result_type, args);
        }

        public virtual IndefiniteVarType CallFromArray(UsableLuaType[] result_type, object[] args)
        {

            lock (env)
            {
                var param = new IndefiniteVarType(args);
                env.Current.RawGetInteger(LuaRegistry.Index, RefId);
                param.PushToLuaStack(env);
                var stats = env.Current.PCall(param.Count, result_type.Length, 0);
                if (stats != LuaStatus.OK)
                {
                    throw new LuaException("call lua function error:" + env.Current.ToString(-1), env);
                }
                return IndefiniteVarType.FetchFromLuaStack(env, result_type);
            }
        }

        public virtual IndefiniteVarType CallFromArray(int return_num, object[] args)
        {
            lock (env)
            {
                var param = new IndefiniteVarType(args);
                env.Current.RawGetInteger(LuaRegistry.Index, RefId);
                param.PushToLuaStack(env);
                var stats = env.Current.PCall(param.Count, return_num, 0);
                if (stats != LuaStatus.OK)
                {
                    throw new LuaException("call lua function error:" + env.Current.ToString(-1), env);
                }
                return IndefiniteVarType.FetchFromLuaStack(env, return_num);
            }
        }

        public void Move(out int ref_id, out EnvLua env)
        {
            ref_id = this.ref_id ?? throw new Exception("unexpected error: luafunction has not been initalized when disposing");
            env = this.env;
            Dispose();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                // 释放非托管资源
                if (ref_id.HasValue)
                {
                    lock (env)
                    {
                        env.Current.Unref(LuaRegistry.Index, ref_id.Value);
                    }
                }
                disposed = true;
                // if (disposing)
                // {
                //     // 释放托管资源
                //     ref_id = null;
                //     env = null;
                // }
            }
        }

        ~ExLuaFunction()
        {
            Dispose(false);
        }

        public void PushToLuaStack()
        {
            lock (env)
            {
                env.Current.RawGetInteger(LuaRegistry.Index, RefId);
            }
        }
    }
}